/*
 * RawFTPClient.java
 * 2007-12-5 ����09:56:48
 * Author: Lex
 * Note: 
 */
package org.lex.net.ftp;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;

import org.lex.net.event.ProtocolEventSupport;
import org.lex.net.event.ProtocolListener;
import org.lex.net.ftp.util.FTPUtilities;
import org.lex.net.telnet.TelnetClient;
import org.lex.net.uitl.NetUtilities;


public class RawFTPClient extends TelnetClient {
	public static final int DEFAULT_FTP_PORT = 21;
	protected static final int FTP_SUCCESS = 1;
	protected static final int FTP_TRY_AGAIN = 2;
	protected static final int FTP_ERROR = 3;
	// ======================================
	protected String lastCommand = null;
	protected String serverName;
	protected boolean replyPending;
	protected boolean binaryMode;
	protected boolean isLoggedIn;
	public String welcomeMsg;
	private int lastReplyCode;
	protected FTPResponse lastResponse = null;
	// ------------------------------------
	protected ProtocolEventSupport eventSupport = null;

	public RawFTPClient() {
		super();
		serverName = null;
		replyPending = false;
		binaryMode = false;
		isLoggedIn = false;
		this.lastResponse = new FTPResponse();
		this.eventSupport = new ProtocolEventSupport(this);
		this.setSoTimeout(500);
	}

	/***************************************************************************
	 * ProtocolListener event support
	 **************************************************************************/
	public void addProtocolListener(ProtocolListener l) {
		eventSupport.addProtocolListener(l);
	}

	public void removeProtocolListener(ProtocolListener l) {
		eventSupport.removeProtocolListener(l);
	}

	protected void fireCommandSent(String command, String message) {
		eventSupport.fireCommandSent(command, message);
	}

	protected void fireReplyRecived(int replyCode, String message) {
		this.eventSupport.fireReplyRecived(replyCode, message);
	}

	/***************************************************************************
	 * Connect & Disconnect
	 **************************************************************************/
	public void openServer(String host) throws UnknownHostException, IOException {
		this.openServer(host, DEFAULT_FTP_PORT);
	}

	public void openServer(String host, int port) throws UnknownHostException, IOException {
		this.openServer(InetAddress.getByName(host), port);
	}

	public void openServer(InetAddress host, int port) throws IOException {
		serverName = host.getHostAddress();
		super.openServer(host, port);
		if (readReplyCode() == FTP_ERROR)
			throw new FTPCommandException("Welcome message: " + getLastResponseString());
	}

	public void closeServer() throws IOException {
		if (true == isServerOpen()) {
			issueCommandAndReadResponse("QUIT");
			super.closeServer();
		}
	}

	@Override
	public boolean isServerOpen() {
		return this.isLoggedIn && super.isServerOpen();
	}

	public void login(String user, char[] pswd) throws IOException {
		if (false == super.isServerOpen())
			throw new FTPLoginException("not connected to host");
		if (null == user || 0 == user.length())
			user = "anonymous";
		if (null == pswd || 0 == pswd.length)
			pswd = "anonymous@mail.org".toCharArray();
		// FIXME ����Ҫ�޸�!���벻Ӧ�ó����� String ��
		if (issueCommandAndReadResponse("USER", user) == FTP_ERROR)
			throw new FTPLoginException("user " + user + " : " + getLastResponseString());
		if (lastReplyCode == 331 && (issueCommandAndReadResponse("PASS", new String(pswd)) == FTP_ERROR))
			throw new FTPLoginException("password: " + getLastResponseString());
		welcomeMsg = this.getLastResponseString();
		isLoggedIn = true;
	}

	protected int issueCommandAndReadResponse(String cmd) throws IOException {
		return this.issueCommandAndReadResponse(cmd, null);
	}

	protected int issueCommandAndReadResponse(String cmd, String args) throws IOException {
		if (null == args || 0 == args.length())
			lastCommand = cmd;
		else
			lastCommand = cmd + " " + args;
		while (true == replyPending) {
			replyPending = false;
			if (readReplyCode() == FTP_ERROR)
				throw new FTPCommandException("Error reading FTP pending reply\n");
		}
		int code;
		do {
			sendServer(lastCommand);
			this.fireCommandSent(cmd, lastCommand);
			code = readReplyCode();
		} while (code == FTP_TRY_AGAIN);
		return code;
	}

	protected void issueCommandAndReadResponseAndCheckReplyCode(String cmd) throws IOException {
		this.issueCommandAndReadResponseAndCheckReplyCode(cmd, null);
	}

	protected void issueCommandAndReadResponseAndCheckReplyCode(String cmd, String args) throws IOException {
		if (issueCommandAndReadResponse(cmd, args) != FTP_SUCCESS)
			throw new FTPCommandException(cmd + " " + args + ":" + getLastResponseString());
	}

	/***************************************************************************
	 * ftp response related method
	 **************************************************************************/
	public int getLastReplyCode() {
		return this.lastReplyCode;
	}

	public FTPResponse getLastResponse() {
		return this.lastResponse;
	}

	public String getLastResponseString() {
		return lastResponse.toString();
	}

	protected int readReplyCode() throws IOException {
		lastReplyCode = this.readResponse().getReplyCode();
		replyPending = false;
		this.fireReplyRecived(lastReplyCode, this.lastResponse.toString());
		switch (lastReplyCode / 100) {
		case 1:
			replyPending = true;
			// fall through
		case 2:
		case 3:
			return FTP_SUCCESS;
		case 5:
			if (lastReplyCode == 530)
				if (!isLoggedIn)
					throw new FTPLoginException("Not logged in");
				else
					return FTP_ERROR;
			if (lastReplyCode == 550)
				throw new FileNotFoundException(lastCommand + ": " + getLastResponseString());
			// fall through
		case 4:
		default:
			return FTP_ERROR;
		}
	}

	/**
	 * ��ȡ���������ص���Ϣ.<br>
	 * Ӧ��С��ʹ�ã�ֻ��ȷ���������з�����Ϣʱ��Ӧ�õ����������
	 * ��Ȼ��⡢�׳��������쳣��ʹ��(�˷�)��ʱ�佫���ܻ�Ӱ���������. <br>
	 * �������Ϣ���ᱻ���浽<code>response</code>��,�����ظ�ʹ��<code>
	 * getLastResponse()</code> ����ȡ���������һ�η��ص���Ϣ.<br>
	 * ÿ�ε��ø÷���������ջ��棬Ȼ��д���µķ�����������Ϣ.
	 * 
	 * @return Response ���������ص���Ϣ,�ı���ʽ��
	 * @throws IOException
	 * @see #getLastResponse()
	 */
	private FTPResponse readResponse() throws IOException {
		lastResponse = new FTPResponse();
		StringBuilder buf = new StringBuilder();
		do {
			int ch = serverReader.read();
			if (-1 == ch)
				break;
			if ('\r' == ch)
				continue;
			else if ('\n' == ch) {
				String line = buf.toString();
				lastResponse.addLine(line);
				if (true == NetUtilities.hasReplyCode(line)) {
					if ('-' != buf.charAt(3))
						break;
				}
				buf.setLength(0);
			} else {
				buf.append((char) ch);
			}
		} while (true);
		return lastResponse;
	}

	/***************************************************************************
	 * Data Connection Method
	 **************************************************************************/
	protected Socket openDataConnection(String cmd) throws IOException {
		Socket pasv = null;
		// we try to use pasv mode first
		try {
			pasv = openPassiveDataConnection();
		} catch (IOException e) {
		}
		if (pasv != null) {
			try {
				if (issueCommandAndReadResponse(cmd) == FTP_ERROR) {
					NetUtilities.closeQuietly(pasv);
					throw new FTPCommandException(getLastResponseString());
				} else {
					return pasv;
				}
			} catch (IOException e) {
				NetUtilities.closeQuietly(pasv);
				throw e;
			}
		} else {
			// pasv failed, we try to use active mode
			ServerSocket ssocket = new ServerSocket(0, 1);
			InetAddress local = ssocket.getInetAddress();
			if (local.isAnyLocalAddress())
				local = getClientAddress();
			String port = FTPUtilities.generatePortCommand(local, ssocket.getLocalPort());
			if (issueCommandAndReadResponse(port) == FTP_ERROR)
				throw new FTPCommandException("PORT :" + getLastResponseString());
			if (issueCommandAndReadResponse(cmd) == FTP_ERROR)
				throw new FTPCommandException(cmd + ":" + getLastResponseString());
			ssocket.setSoTimeout(this.getConnectTimeout());
			Socket dc = ssocket.accept();
			dc.setSoTimeout(this.getSoTimeout());
			NetUtilities.closeQuietly(ssocket);
			return dc;
		}
	}

	protected Socket openPassiveDataConnection() throws IOException {
		if (issueCommandAndReadResponse("PASV") == FTP_ERROR)
			throw new FTPCommandException("PASV failed: " + getLastResponseString());
		SocketAddress server = FTPUtilities.parsePasv(getLastResponseString());
		Socket pasv = this.getSocketFactory().createSocket();
		pasv.setSoTimeout(this.getSoTimeout());
		pasv.connect(server, this.getConnectTimeout());
		return pasv;
	}
}
